import rsa
import os
import sys
import subprocess as psutil
from datetime import datetime
from base64 import b64decode
from packaging.version import Version 
from utils import get_hwid, aes_dec

aes_key = 'TZIre7sxwMHYIThSW9IJaFEFXU7dSFDrFQrizqEPbPdu0QADHCoRegYqljwgQPxExzNymBUaXpKKMXH67AQZaxgl2Nm5tRWUxIbZR5uPkSWLve89FMtZBuBGJAI7gm1kW4f21NSKi6Anal6ZRV7wyf2fNRSNULxcDlP38jHW9OsVZ4ZwVNZJs0Y6AtjfCuhGYDK3dfJlCq0gpLW6uT91PIQlkdRtPMMBYx5WCcUMEJzAn0WFXz1Sa2XWZ76qJAvp'
#BASE32(CONCAT(PRIVATE_KEY_ENCRYPTED(HASH(DATA)), DATA))

class LicDataError(Exception):
    def __init__(self, desc):
        self.desc = desc

    def __str__(self):
        return repr(self.desc)


class LicDateInvalid(Exception):
    def __init__(self, desc):
        self.desc = desc

    def __str__(self):
        return repr(self.desc)


class LicItemError(Exception):
    def __init__(self, desc):
        self.desc = desc

    def __str__(self):
        return repr(self.desc)


def new_keys(product, nbits=256):
    fpath = '{}_private'.format(product)
    keys = None
    try:
        with open(fpath, 'rb') as fp:
            pri_key = rsa.key.PrivateKey.load_pkcs1(fp.read())
            pub_key = rsa.key.PublicKey(n=pri_key.n, e=65537)
            keys = pub_key, pri_key
    except Exception as e:
        print(e)
        with open(fpath, 'wb') as fp:
            keys = rsa.newkeys(nbits)
            fp.write(keys[1].save_pkcs1())
    return keys

def get_pub_key(message:bytes) -> bytes:
    try:
        si = message.index(b'<pub_key>') 
        ei = message.index(b'</pub_key>')
        return aes_dec(aes_key[::8].encode(), message[si+len(b'<pub_key>'):ei]), si
    except:
        pass
    return b'', 0

def get_lic_info(licfile):
    try:
        with open(licfile, 'rb') as fp:
            encdata = b64decode(fp.read())
            message, sig = encdata[256:], encdata[:256]
            pub_key, index = get_pub_key(message)
            if not pub_key:
                raise LicDataError('invalid public key')
            pub_key = rsa.PublicKey.load_pkcs1(pub_key)
            if rsa.verify(message, sig, pub_key):
                return dict([msg.split(':') for msg in message[:index].decode().split(';') if msg])
    except rsa.VerificationError:
        raise LicDataError('invalid key')
    except FileNotFoundError:
        raise LicDataError('no key data exist')


class LicItemMatcher:
    def __init__(self, name, locval):
        self.name = name
        self.locval = locval

    def match(self, licval, locval):
        if licval != locval:
            raise LicItemError('%s is mismatched' % (self.name, ))

    def test(self, lic):
        licval = lic.pop(self.name)
        if not licval:
            raise LicItemError('%s is missing' % (self.name, ))
        self.match(licval, self.locval)


class DateMatcher(LicItemMatcher):
    def match(self, licval, locval):
        edate = licval
        locval = locval.timestamp()
        if locval > float(edate):
            raise LicDateInvalid('license is expired')


class VersionMatcher(LicItemMatcher):
    def match(self, licval, locval):
        if Version(locval) != Version(licval):
            raise LicItemError('version is mismatched')


def check_lic(licfile, product, version):
    # machine code mismatched
    try:
        locrec = {
            'maccode': LicItemMatcher('maccode', get_hwid()), 
            'product': LicItemMatcher('product', product),
            'expire-date': DateMatcher('expire-date',
                           datetime.utcfromtimestamp(
                           datetime.now().timestamp())),
            'version': VersionMatcher('version', version)}
        licrec = get_lic_info(licfile)
        for k, v in locrec.items():
            v.test(licrec)
    except:
        raise


if __name__ == '__main__':
    maccode, product, ver = get_hwid(), 'AlgWeb', '1.0'
    try:
        licinfo = get_lic_info('{}.lic'.format(product))
        print(licinfo)
        check_lic('{}.lic'.format(product), product, ver)
        print('license is valid')
    except Exception as e:
        print(e)
